/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.core;

import hextd.HexTD;
import hextd.blackBox.BlackBox;
import hextd.core.gameloop.GameLoopManager;
import hextd.hex.Hex;
import hextd.creep.Creep;
import hextd.tower.Tower;
import hextd.tower.TowerFactory;
import hextd.util.MapIO;
import hextd.util.TowerListener;
import hextd.util.WaveStartListener;
import hextd.wave.Path;
import hextd.wave.Wave;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author hylke
 */
public class ContextHeadless extends Context {

	private URL contextUrl;
	private URL mapContextUrl;
	private int nextUniqueSpriteId = 0;
	//
	private String levelTitle = "";
	private String levelInfo = "";
	private String backgroundImage = "";
	private int livesStart = 20;
	private long creditsStart = 0;
	private int interestStart = 3;
	private int interestIncrease = 3;
	//
	private GameLoopManager gameLoop;
	private GameGrid gameGrid;
	private ArrayList<Creep> enemies;
	private ArrayList<Tower> towers;
	private int enemyCount = 0;
	private ArrayList<Path> paths;
	private int wave = 0;
	private ArrayList<Wave> waves;
	private boolean waveActive = false;
	//
	private ArrayList<ContextListener> contextListeners;
	private ArrayList<TowerListener> towerListeners;
	private ArrayList<WaveStartListener> waveListeners;
	//
	private MapLoader mapLoader;

	public ContextHeadless() {
		super();
		this.gameGrid = new GameGrid(this);
		this.contextListeners = new ArrayList<ContextListener>();
		this.towerListeners = new ArrayList<TowerListener>();
		this.waveListeners = new ArrayList<WaveStartListener>();
		this.enemies = new ArrayList<Creep>();
		this.towers = new ArrayList<Tower>();
		this.paths = new ArrayList<Path>();
		this.waves = new ArrayList<Wave>();
	}

	@Override
	public GameTime getGameTime() {
		return gameTime;
	}

	@Override
	public void endGame() {
		this.setGameResult(HexTD.GameResult.ended);
	}

	@Override
	public HexTD.GameResult getGameResult() {
		return gameResult;
	}

	@Override
	public void setGameResult(HexTD.GameResult gameResult) {
		this.gameResult = gameResult;
	}

	public void setMapLoader(MapLoader mapLoader) {
		this.mapLoader = mapLoader;
	}

	// Clears out all map and game data from the context to make it ready for loading a new map.
	public synchronized void cleanContext() {
		this.setWaveActive(false);

		gameGrid = null;
		removeAllEnemies();
		clearTowers();
		clearPlayers();

		wave = 0;
		waves.clear();

		paths.clear();
		setLivesStart(20);
		setInterestIncrease(3);
		setInterestStart(3);
		setLevelInfo("");
		setLevelTitle("");
		setBackgroundImage("");

		blackBox.clear();
	}

	@Override
	public synchronized boolean loadMap(String maplocation, String expectedMD5) {
		boolean retval = true;
		System.out.println("ContextHeadless::loadMap 1: Loading map " + maplocation);

		setGameResult(HexTD.GameResult.loading);
		cleanContext();

		boolean loadFromServer = true;
		int mapId = 0;
		try {
			mapId = Integer.parseInt(maplocation);
		} catch (NumberFormatException e) {
			loadFromServer = false;
		}
		if (loadFromServer) {
			retval = loadMap(mapLoader.getMap(mapId), expectedMD5);
			if (!retval) {
				return retval;
			}
		} else {
			System.err.println("ContextNormal::loadMap 1: Non numeric map name: " + maplocation);
			//retval = MapIO.readLevel(this, maplocation);
		}

		finishLoading();
		return retval;
	}

	@Override
	public synchronized boolean loadMap(MapInfo map, String expectedMD5) {
		loadedMapInfo = map;

		if (expectedMD5 != null && expectedMD5.length() > 0) {
			map.expectedMD5 = expectedMD5;
		}

		if (map.getContent() != null) {
			System.out.println("ContextNormal::loadMap 1: Loading map (" + map.mapId + ") of size " + map.getContent().length());
			MapIO.loadLevel(this, map);
			return true;
		} else {
			setGameResult(HexTD.GameResult.waitingForMap);
			return false;
		}
	}

	public synchronized void finishLoading() {
		for (Path p : this.paths) {
			int i = p.getOwner();
			if (i >= 0) {
				this.ensurePlayer(i);
				ContextPlayer cp = getPlayer(i);
				cp.addPath(p);
			}
		}
		for (Tower t : this.towers) {
			t.resetScale();
		}
		this.resetCredits(this.creditsStart, 0);
		this.setInterest(this.interestStart, this.interestIncrease);
		this.resetLives(this.livesStart);
		this.resetScore();
		this.resetTicks();
		this.gameTime.reset();
		setWaveReady(true);
		setGameResult(HexTD.GameResult.inactive);
	}

	@Override
	public MapInfo getMapInfo() {
		return loadedMapInfo;
	}

	@Override
	public void messageSend(String line) {
	}

	@Override
	public void resetMap() {
		System.err.println("ContextHeadless::resetMap: WARNING: resetMap needs refactoring");

		this.removeAllEnemies();
		this.clearTowers();
		//MapIO.readLevel(this, new BufferedReader(new StringReader(this.levelData)));
		this.resetCredits(this.creditsStart, 0);
		this.setInterest(this.interestStart, this.interestIncrease);
		this.resetLives(this.livesStart);
		this.resetScore();
		this.wave = 0;
		setWaveReady(true);
		this.resetTicks();
		this.gameTime.reset();
	}

	@Override
	public int getEnemyCount() {
		return this.enemyCount;
	}

	@Override
	public synchronized int getUniqueSpriteId() {
		return nextUniqueSpriteId++;
	}

	@Override
	public void addEnemies(Collection<Creep> newEnemies) {
		for (int i = this.enemies.size() - 1; i >= 0; i--) {
			Creep e = enemies.get(i);
			if (e.isDead()) {
				enemies.remove(i);
				int target = e.getTarget();
				ContextPlayer cp = getPlayer(target);
				if (cp != null) {
					cp.removeTarget(e);
				}
			}
		}
		this.enemies.addAll(newEnemies);
		for (Creep e : newEnemies) {
			int target = e.getTarget();
			ContextPlayer cp = getPlayer(target);
			if (cp != null) {
				cp.addTarget(e);
			}
		}
		this.fireWaveStartedEvent();
	}

	@Override
	public ArrayList<Creep> getEnemies() {
		return this.enemies;
	}

	@Override
	public void enemyDied() {
		enemyCount--;
		if (enemyCount <= 0) {
			payInterest();
			gameLoop.resetGraceTicks();
			setWaveReady(true);
			setWaveActive(false);
			if (wave >= waves.size()) {
				blackBox.endGame(-1, false);
			}
		}
	}

	@Override
	public void removeAllEnemies() {
		if (this.enemyCount > 0) {
			this.enemyCount = 0;
		}
		enemies.clear();
		for (ContextPlayer p : getPlayers()) {
			p.removeAllTargets();
		}
	}

	public void setEnemyCount(int c) {
		this.enemyCount = c;
	}

	@Override
	public ArrayList<Wave> getWaves() {
		return this.waves;
	}

	@Override
	public int getWave() {
		return this.wave;
	}

	@Override
	public int getWaveCount() {
		return this.waves.size();
	}

	@Override
	public void nextWave() {
		Wave w = waves.get(wave);
		removeAllEnemies();
		addEnemies(w.getEnemies());
		wave++;
		waveActive = true;
		setEnemyCount(w.enemyCount());
		fireWaveStartedEvent();
		w.clear(); // We've got the creeps, the wave object can free 'em
	}

	@Override
	public void addWaveStartListener(WaveStartListener l) {
		this.waveListeners.add(l);
	}

	@Override
	public void removeWaveStartListener(WaveStartListener l) {
		this.waveListeners.remove(l);
	}

	private void fireWaveStartedEvent() {
		for (int i = 0; i < this.waveListeners.size(); i++) {
			this.waveListeners.get(i).waveStarted();
		}
	}

	@Override
	public void setDebug(String string) {
	}

	@Override
	public void setStatus(String string) {
		System.out.println("ContextHeadless:setStatus: " + string);
	}

	@Override
	public void playerDied(ContextPlayer cp) {
		int activePlayers = getActivePlayers();
		activePlayers--;
		setActivePlayers(activePlayers);

		System.out.println("ContextHeadless:playerDied. Left: " + activePlayers);
		if (cp != null) {
			cp.setActive(false);
		}

		if (this.getGameState() == HexTD.GameState.spPlaying && activePlayers <= 0) {
			this.setGameState(HexTD.GameState.spGameEnd);
			this.blackBox.endGame(-1, false);
		} else if (this.getGameState() == HexTD.GameState.mpPlaying && activePlayers <= 1) {
			this.setGameState(HexTD.GameState.mpGameEnd);
			this.blackBox.endGame(-1, false);
		} else if ((gameState == HexTD.GameState.checking || gameState == HexTD.GameState.replaying)) {
			if (activePlayers <= 1) {
				setGameState(HexTD.GameState.checkingEnd);
				blackBox.endGame(-1, false);
			}
		}
		for (Creep e : cp.getTargets()) {
			e.remove();
		}
		for (Tower t : cp.getTowers()) {
			t.queueSell(gameTime.getTime() + actionDelay);
		}
	}

	@Override
	public void addTower(Tower t) {
		towers.add(t);
		Collections.sort(towers, TowerFactory.towerComparator);

		int owner = t.getOwner();
		ensurePlayer(owner);

		ContextPlayer cp = getPlayer(t.getOwner());
		cp.addTower(t);
		fireTowerAddedEvent(t);
	}

	@Override
	public void removeTower(Tower t) {
		this.towers.remove(t);
		ContextPlayer p = getPlayer(t.getOwner());
		p.removeTower(t);
		this.fireTowerRemovedEvent(t);
	}

	@Override
	public void newTower(int col, int row, TowerFactory.type t) {
		blackBox.newTower(-1, col, row, t, this.getLocalPlayerId(), -1, true);
	}

	@Override
	public void sellTower(Tower t) {
		int[] hexC = t.getHex().getLocation();
		this.blackBox.sellTower(-1, hexC[0], hexC[1], -1, t.getOwner(), true);

	}

	@Override
	public void clearTowers() {
		Tower t;
		for (int i = this.towers.size() - 1; i >= 0; i--) {
			t = this.towers.get(i);
			Hex hex = t.getHex();
			hex.unSetTower();
			t.doCleanup();
			this.towers.remove(t);
			this.fireTowerRemovedEvent(t);
		}
	}

	@Override
	public void nextMode(Tower t, int m, int step) {
		int[] hexC = t.getHex().getLocation();
		blackBox.setMode(-1, hexC[0], hexC[1], m, t.getMode(m) + step, -1, t.getOwner(), true);
	}

	@Override
	public void setMode(Tower t, int m, int v) {
		int[] hexC = t.getHex().getLocation();
		this.blackBox.setMode(-1, hexC[0], hexC[1], m, v, -1, t.getOwner(), true);
	}

	@Override
	public void addTowerListener(TowerListener l) {
		this.towerListeners.add(l);
	}

	@Override
	public void removeTowerListener(TowerListener l) {
		this.towerListeners.remove(l);
	}

	private void fireTowerAddedEvent(Tower t) {
		for (int i = 0; i < this.towerListeners.size(); i++) {
			this.towerListeners.get(i).towerBuild(t);
		}
	}

	private void fireTowerRemovedEvent(Tower t) {
		for (int i = 0; i < this.towerListeners.size(); i++) {
			this.towerListeners.get(i).towerRemoved(t);
		}
	}

	@Override
	public void fireMoneyChangedEvent(ContextPlayer player) {
		for (int i = 0; i < this.contextListeners.size(); i++) {
			this.contextListeners.get(i).moneyChanged(player);
		}
	}

	@Override
	public void fireLivesChangedEvent(ContextPlayer player) {
		for (int i = 0; i < this.contextListeners.size(); i++) {
			this.contextListeners.get(i).livesChanged(player);
		}
	}

	@Override
	public void hexChanged() {
	}

	@Override
	public ArrayList<Path> getPaths() {
		return this.paths;
	}

	@Override
	public Path getPath(int p) {
		if (p < 0) {
			p = 0;
		}
		if (p > this.paths.size() - 1) {
			p = this.paths.size() - 1;
		}
		return this.paths.get(p);
	}

	@Override
	public Collection<Path> getPaths(int player) {
		this.ensurePlayer(player);
		ContextPlayer cp = getPlayer(player);
		if (cp != null) {
			return cp.getPaths();
		} else {
			System.err.println("Context::getLives: Calling getLives on non-existing payer " + player);
			return null;
		}
	}

	@Override
	public void showAlert(String alert) {
		System.err.print("ContextHeadless::showAlert: " + alert + "\n");
	}

	@Override
	public boolean isWaveActive() {
		return waveActive;
	}

	@Override
	public void setWaveActive(boolean waveActive) {
		this.waveActive = waveActive;
	}

	public int getWinnerId() {
		int winner = -1;
		for (ContextPlayer p : getPlayers()) {
			if (p.isActive()) {
				if (winner == -1) {
					winner = p.getUserId();
				} else if (winner > -1) {
					// Two active players?? No winner!
					winner = -2;
				}
			}
		}
		return winner;
	}

	public void setInterest(int start, int increase) {
		this.interestStart = start;
		this.interestIncrease = increase;
		for (ContextPlayer cp : getPlayers()) {
			cp.setInterestStart(interestStart);
			cp.setInterestIncrease(interestIncrease);
		}
	}

	@Override
	public HexTD.GameState getGameState() {
		return gameState;
	}

	@Override
	public void setGameState(HexTD.GameState gameState) {
		this.gameState = gameState;
	}

	@Override
	public URL getContextUrl() {
		return contextUrl;
	}

	@Override
	public void setContextUrl(URL contextUrl) {
		this.contextUrl = contextUrl;
		if (mapContextUrl == null) {
			try {
				mapContextUrl = new URL(contextUrl, "maps/");
			} catch (MalformedURLException ex) {
				Logger.getLogger(ContextHeadless.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}

	@Override
	public URL getMapContextUrl() {
		return mapContextUrl;
	}

	@Override
	public void setMapContextUrl(URL mapContextUrl) {
		this.mapContextUrl = mapContextUrl;
	}

	@Override
	public ArrayList<Tower> getTowers() {
		return towers;
	}

	@Override
	public String getMapLocation() {
		if (loadedMapInfo != null) {
			if (loadedMapInfo.mapId == 0) {
				return "Custom";
			} else {
				return "" + loadedMapInfo.mapId;
			}
		} else {
			return "";
		}
	}

	@Override
	public String getLevelTitle() {
		return levelTitle;
	}

	@Override
	public void setLevelTitle(String levelTitle) {
		this.levelTitle = levelTitle;
	}

	@Override
	public String getLevelInfo() {
		return levelInfo;
	}

	@Override
	public void setLevelInfo(String levelInfo) {
		this.levelInfo = levelInfo;
	}

	@Override
	public String getBackgroundImage(boolean canDefault) {
		return backgroundImage;
	}

	@Override
	public void setBackgroundImage(String backgroundImage) {
		this.backgroundImage = backgroundImage;
	}

	@Override
	public int getLivesStart() {
		return livesStart;
	}

	@Override
	public void setLivesStart(int livesStart) {
		this.livesStart = livesStart;
	}

	@Override
	public long getCreditsStart() {
		return creditsStart;
	}

	@Override
	public void setCreditsStart(long creditsStart) {
		this.creditsStart = creditsStart;
	}

	@Override
	public int getInterestStart() {
		return interestStart;
	}

	@Override
	public void setInterestStart(int interestStart) {
		this.interestStart = interestStart;
	}

	@Override
	public int getInterestIncrease() {
		return interestIncrease;
	}

	@Override
	public void setInterestIncrease(int interestIncrease) {
		this.interestIncrease = interestIncrease;
	}

	@Override
	public GameGrid newGameGrid() {
		gameGrid = new GameGrid(this);
		return gameGrid;
	}

	@Override
	public GameGrid getGameGrid() {
		return gameGrid;
	}

	@Override
	public BlackBox getBlackBox() {
		return blackBox;
	}

	@Override
	public void setBlackBox(BlackBox blackBox) {
		this.blackBox = blackBox;
	}

	/**
	 * @return the gameLoop
	 */
	public GameLoopManager getGameLoop() {
		return gameLoop;
	}

	/**
	 * @param gameLoop the gameLoop to set
	 */
	@Override
	public void setGameLoop(GameLoopManager gameLoop) {
		this.gameLoop = gameLoop;
	}
}
